home *** CD-ROM | disk | FTP | other *** search
/ Clickx 63 / Clickx 63.iso / software / multimedia / mirov204 / Miro_Installer.exe / xulrunner / modules / Microformats.js < prev    next >
Encoding:
Text File  |  2009-03-29  |  62.9 KB  |  1,791 lines

  1. var EXPORTED_SYMBOLS = ["Microformats", "adr", "tag", "hCard", "hCalendar", "geo"];
  2.  
  3. var Microformats = {
  4.   /* When a microformat is added, the name is placed in this list */
  5.   list: [],
  6.   /* Custom iterator so that microformats can be enumerated as */
  7.   /* for (i in Microformats) */
  8.   __iterator__: function () {
  9.     for (let i=0; i < this.list.length; i++) {
  10.       yield this.list[i];
  11.     }
  12.   },
  13.   /**
  14.    * Retrieves microformats objects of the given type from a document
  15.    * 
  16.    * @param  name          The name of the microformat (required)
  17.    * @param  rootElement   The DOM element at which to start searching (required)
  18.    * @param  options       Literal object with the following options:
  19.    *                       recurseExternalFrames - Whether or not to search child frames
  20.    *                       that reference external pages (with a src attribute)
  21.    *                       for microformats (optional - defaults to true)
  22.    *                       showHidden -  Whether or not to add hidden microformat
  23.    *                       (optional - defaults to false)
  24.    *                       debug - Whether or not we are in debug mode (optional
  25.    *                       - defaults to false)
  26.    * @param  targetArray  An array of microformat objects to which is added the results (optional)
  27.    * @return A new array of microformat objects or the passed in microformat 
  28.    *         object array with the new objects added
  29.    */
  30.   get: function(name, rootElement, options, targetArray) {
  31.     function isAncestor(haystack, needle) {
  32.       var parent = needle;
  33.       while (parent = parent.parentNode) {
  34.         /* We need to check parentNode because defaultView.frames[i].frameElement */
  35.         /* isn't a real DOM node */
  36.         if (parent == needle.parentNode) {
  37.           return true;
  38.         }
  39.       }
  40.       return false;
  41.     }
  42.     if (!Microformats[name] || !rootElement) {
  43.       return;
  44.     }
  45.     targetArray = targetArray || [];
  46.  
  47.     /* Root element might not be the document - we need the document's default view */
  48.     /* to get frames and to check their ancestry */
  49.     var defaultView = rootElement.defaultView || rootElement.ownerDocument.defaultView;
  50.     var rootDocument = rootElement.ownerDocument || rootElement;
  51.  
  52.     /* If recurseExternalFrames is undefined or true, look through all child frames for microformats */
  53.     if (!options || !options.hasOwnProperty("recurseExternalFrames") || options.recurseExternalFrames) {
  54.       if (defaultView && defaultView.frames.length > 0) {
  55.         for (let i=0; i < defaultView.frames.length; i++) {
  56.           if (isAncestor(rootDocument, defaultView.frames[i].frameElement)) {
  57.             Microformats.get(name, defaultView.frames[i].document, options, targetArray);
  58.           }
  59.         }
  60.       }
  61.     }
  62.  
  63.     /* Get the microformat nodes for the document */
  64.     var microformatNodes = [];
  65.     if (Microformats[name].className) {
  66.       microformatNodes = Microformats.getElementsByClassName(rootElement,
  67.                                         Microformats[name].className);
  68.       /* alternateClassName is for cases where a parent microformat is inferred by the children */
  69.       /* If we find alternateClassName, the entire document becomes the microformat */
  70.       if ((microformatNodes.length == 0) && Microformats[name].alternateClassName) {
  71.         var altClass = Microformats.getElementsByClassName(rootElement, Microformats[name].alternateClassName);
  72.         if (altClass.length > 0) {
  73.           microformatNodes.push(rootElement); 
  74.         }
  75.       }
  76.     } else if (Microformats[name].attributeValues) {
  77.       microformatNodes =
  78.         Microformats.getElementsByAttribute(rootElement,
  79.                                             Microformats[name].attributeName,
  80.                                             Microformats[name].attributeValues);
  81.       
  82.     }
  83.     /* Create objects for the microformat nodes and put them into the microformats */
  84.     /* array */
  85.     for (let i = 0; i < microformatNodes.length; i++) {
  86.       /* If showHidden undefined or false, don't add microformats to the list that aren't visible */
  87.       if (!options || !options.hasOwnProperty("showHidden") || !options.showHidden) {
  88.         if (microformatNodes[i].ownerDocument) {
  89.           if (microformatNodes[i].getBoundingClientRect) {
  90.             var box = microformatNodes[i].getBoundingClientRect();
  91.             box.width = box.right - box.left;
  92.             box.height = box.bottom - box.top;
  93.           } else {
  94.             var box = microformatNodes[i].ownerDocument.getBoxObjectFor(microformatNodes[i]);
  95.           }
  96.           if ((box.height == 0) || (box.width == 0)) {
  97.             continue;
  98.           }
  99.         }
  100.       }
  101.       try {
  102.         if (options && options.debug) {
  103.           /* Don't validate in the debug case so that we don't get errors thrown */
  104.           /* in the debug case, we want all microformats, even if they are invalid */
  105.           targetArray.push(new Microformats[name].mfObject(microformatNodes[i], false));
  106.         } else {
  107.           targetArray.push(new Microformats[name].mfObject(microformatNodes[i], true));
  108.         }
  109.       } catch (ex) {
  110.         /* Creation of individual object probably failed because it is invalid. */
  111.         /* This isn't a problem, because the page might have invalid microformats */
  112.       }
  113.     }
  114.     return targetArray;
  115.   },
  116.   /**
  117.    * Counts microformats objects of the given type from a document
  118.    * 
  119.    * @param  name          The name of the microformat (required)
  120.    * @param  rootElement   The DOM element at which to start searching (required)
  121.    * @param  options       Literal object with the following options:
  122.    *                       recurseExternalFrames - Whether or not to search child frames
  123.    *                       that reference external pages (with a src attribute)
  124.    *                       for microformats (optional - defaults to true)
  125.    *                       showHidden -  Whether or not to add hidden microformat
  126.    *                       (optional - defaults to false)
  127.    *                       debug - Whether or not we are in debug mode (optional
  128.    *                       - defaults to false)
  129.    * @return The new count
  130.    */
  131.   count: function(name, rootElement, options) {
  132.     var mfArray = Microformats.get(name, rootElement, options);
  133.     if (mfArray) {
  134.       return mfArray.length;
  135.     }
  136.     return 0;
  137.   },
  138.   /**
  139.    * Returns true if the passed in node is a microformat. Does NOT return true
  140.    * if the passed in node is a child of a microformat.
  141.    *
  142.    * @param  node          DOM node to check
  143.    * @return true if the node is a microformat, false if it is not
  144.    */
  145.   isMicroformat: function(node) {
  146.     for (let i in Microformats)
  147.     {
  148.       if (Microformats[i].className) {
  149.         if (Microformats.matchClass(node, Microformats[i].className)) {
  150.             return true;
  151.         }
  152.       } else {
  153.         var attribute;
  154.         if (attribute = node.getAttribute(Microformats[i].attributeName)) {
  155.           var attributeList = Microformats[i].attributeValues.split(" ");
  156.           for (let j=0; j < attributeList.length; j++) {
  157.             if (attribute.match("(^|\\s)" + attributeList[j] + "(\\s|$)")) {
  158.               return true;
  159.             }
  160.           }
  161.         }
  162.       }
  163.     }
  164.     return false;
  165.   },
  166.   /**
  167.    * This function searches a given nodes ancestors looking for a microformat
  168.    * and if it finds it, returns it. It does NOT include self, so if the passed
  169.    * in node is a microformat, it will still search ancestors for a microformat.
  170.    *
  171.    * @param  node          DOM node to check
  172.    * @return If the node is contained in a microformat, it returns the parent
  173.    *         DOM node, otherwise returns null
  174.    */
  175.   getParent: function(node) {
  176.     var xpathExpression;
  177.     var xpathResult;
  178.  
  179.     xpathExpression = "ancestor::*[";
  180.     for (let i=0; i < Microformats.list.length; i++) {
  181.       var mfname = Microformats.list[i];
  182.       if (i != 0) {
  183.         xpathExpression += " or ";
  184.       }
  185.       if (Microformats[mfname].className) {
  186.         xpathExpression += "contains(concat(' ', @class, ' '), ' " + Microformats[mfname].className + " ')";
  187.       } else {
  188.         var attributeList = Microformats[mfname].attributeValues.split(" ");
  189.         for (let j=0; j < attributeList.length; j++) {
  190.           if (j != 0) {
  191.             xpathExpression += " or ";
  192.           }
  193.           xpathExpression += "contains(concat(' ', @" + Microformats[mfname].attributeName + ", ' '), ' " + attributeList[j] + " ')";
  194.         }
  195.       }
  196.     }
  197.     xpathExpression += "][1]";
  198.     xpathResult = (node.ownerDocument || node).evaluate(xpathExpression, node, null,  Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null);
  199.     if (xpathResult.singleNodeValue) {
  200.       xpathResult.singleNodeValue.microformat = mfname;
  201.       return xpathResult.singleNodeValue;
  202.     }
  203.     return null;
  204.   },
  205.   /**
  206.    * If the passed in node is a microformat, this function returns a space 
  207.    * separated list of the microformat names that correspond to this node
  208.    *
  209.    * @param  node          DOM node to check
  210.    * @return If the node is a microformat, a space separated list of microformat
  211.    *         names, otherwise returns nothing
  212.    */
  213.   getNamesFromNode: function(node) {
  214.     var microformatNames = [];
  215.     var xpathExpression;
  216.     var xpathResult;
  217.     for (let i in Microformats)
  218.     {
  219.       if (Microformats[i]) {
  220.         if (Microformats[i].className) {
  221.           if (Microformats.matchClass(node, Microformats[i].className)) {
  222.             microformatNames.push(i);
  223.             continue;
  224.           }
  225.         } else if (Microformats[i].attributeValues) {
  226.           var attribute;
  227.           if (attribute = node.getAttribute(Microformats[i].attributeName)) {
  228.             var attributeList = Microformats[i].attributeValues.split(" ");
  229.             for (let j=0; j < attributeList.length; j++) {
  230.               /* If we match any attribute, we've got a microformat */
  231.               if (attribute.match("(^|\\s)" + attributeList[j] + "(\\s|$)")) {
  232.                 microformatNames.push(i);
  233.                 break;
  234.               }
  235.             }
  236.           }
  237.         }
  238.       }
  239.     }
  240.     return microformatNames.join(" ");
  241.   },
  242.   /**
  243.    * Outputs the contents of a microformat object for debug purposes.
  244.    *
  245.    * @param  microformatObject JavaScript object that represents a microformat
  246.    * @return string containing a visual representation of the contents of the microformat
  247.    */
  248.   debug: function debug(microformatObject) {
  249.     function dumpObject(item, indent)
  250.     {
  251.       if (!indent) {
  252.         indent = "";
  253.       }
  254.       var toreturn = "";
  255.       var testArray = [];
  256.       
  257.       for (let i in item)
  258.       {
  259.         if (testArray[i]) {
  260.           continue;
  261.         }
  262.         if (typeof item[i] == "object") {
  263.           if ((i != "node") && (i != "resolvedNode")) {
  264.             if (item[i] && item[i].semanticType) {
  265.               toreturn += indent + item[i].semanticType + " [" + i + "] { \n";
  266.             } else {
  267.               toreturn += indent + "object " + i + " { \n";
  268.             }
  269.             toreturn += dumpObject(item[i], indent + "\t");
  270.             toreturn += indent + "}\n";
  271.           }
  272.         } else if ((typeof item[i] != "function") && (i != "semanticType")) {
  273.           if (item[i]) {
  274.             toreturn += indent + i + "=" + item[i] + "\n";
  275.           }
  276.         }
  277.       }
  278.       if (!toreturn && item) {
  279.         toreturn = item.toString();
  280.       }
  281.       return toreturn;
  282.     }
  283.     return dumpObject(microformatObject);
  284.   },
  285.   add: function add(microformat, microformatDefinition) {
  286.     /* We always replace an existing definition with the new one */
  287.     if (!Microformats[microformat]) {
  288.       Microformats.list.push(microformat);
  289.     }
  290.     Microformats[microformat] = microformatDefinition;
  291.     microformatDefinition.mfObject.prototype.debug =
  292.       function(microformatObject) {
  293.         return Microformats.debug(microformatObject)
  294.       };
  295.   },
  296.   /* All parser specific functions are contained in this object */
  297.   parser: {
  298.     /**
  299.      * Uses the microformat patterns to decide what the correct text for a
  300.      * given microformat property is. This includes looking at things like
  301.      * abbr, img/alt, area/alt and value excerpting.
  302.      *
  303.      * @param  propnode   The DOMNode to check
  304.      * @param  parentnode The parent node of the property. If it is a subproperty,
  305.      *                    this is the parent property node. If it is not, this is the
  306.      *                    microformat node.
  307.      & @param  datatype   HTML/text - whether to use innerHTML or innerText - defaults to text
  308.      * @return A string with the value of the property
  309.      */
  310.     defaultGetter: function(propnode, parentnode, datatype) {
  311.       if (((((propnode.localName.toLowerCase() == "abbr") || (propnode.localName.toLowerCase() == "html:abbr")) && !propnode.namespaceURI) || 
  312.          ((propnode.localName.toLowerCase() == "abbr") && (propnode.namespaceURI == "http://www.w3.org/1999/xhtml"))) && (propnode.getAttribute("title"))) {
  313.         return propnode.getAttribute("title");
  314.       } else if ((propnode.nodeName.toLowerCase() == "img") && (propnode.getAttribute("alt"))) {
  315.         return propnode.getAttribute("alt");
  316.       } else if ((propnode.nodeName.toLowerCase() == "area") && (propnode.getAttribute("alt"))) {
  317.         return propnode.getAttribute("alt");
  318.       } else if ((propnode.nodeName.toLowerCase() == "textarea") ||
  319.                  (propnode.nodeName.toLowerCase() == "select") ||
  320.                  (propnode.nodeName.toLowerCase() == "input")) {
  321.         return propnode.value;
  322.       } else {
  323.         var values = Microformats.getElementsByClassName(propnode, "value");
  324.         /* Verify that values are children of the propnode */
  325.         for (let i = values.length-1; i >= 0; i--) {
  326.           if (values[i].parentNode != propnode) {
  327.             values.splice(i,1);
  328.           }
  329.         }
  330.         if (values.length > 0) {
  331.           var value = "";
  332.           for (let j=0;j<values.length;j++) {
  333.             value += Microformats.parser.defaultGetter(values[j], propnode, datatype);
  334.           }
  335.           return value;
  336.         }
  337.         var s;
  338.         if (datatype == "HTML") {
  339.           s = propnode.innerHTML;
  340.         } else {
  341.           if (propnode.innerText) {
  342.             s = propnode.innerText;
  343.           } else {
  344.             s = propnode.textContent;
  345.           }
  346.         }
  347.         /* If we are processing a value node, don't remove whitespace */
  348.         if (!Microformats.matchClass(propnode, "value")) {
  349.           /* Remove new lines, carriage returns and tabs */
  350.           s    = s.replace(/[\n\r\t]/gi, ' ');
  351.           /* Replace any double spaces with single spaces */
  352.           s    = s.replace(/\s{2,}/gi, ' ');
  353.           /* Remove any double spaces that are left */
  354.           s    = s.replace(/\s{2,}/gi, '');
  355.           /* Remove any spaces at the beginning */
  356.           s    = s.replace(/^\s+/, '');
  357.           /* Remove any spaces at the end */
  358.           s    = s.replace(/\s+$/, '');
  359.         }
  360.         if (s.length > 0) {
  361.           return s;
  362.         }
  363.       }
  364.     },
  365.     /**
  366.      * Used to specifically retrieve a date in a microformat node.
  367.      * After getting the default text, it normalizes it to an ISO8601 date.
  368.      *
  369.      * @param  propnode   The DOMNode to check
  370.      * @param  parentnode The parent node of the property. If it is a subproperty,
  371.      *                    this is the parent property node. If it is not, this is the
  372.      *                    microformat node.
  373.      * @return A string with the normalized date.
  374.      */
  375.     dateTimeGetter: function(propnode, parentnode) {
  376.       var date = Microformats.parser.textGetter(propnode, parentnode);
  377.       if (date) {
  378.         return Microformats.parser.normalizeISO8601(date);
  379.       }
  380.     },
  381.     /**
  382.      * Used to specifically retrieve a URI in a microformat node. This includes
  383.      * looking at an href/img/object/area to get the fully qualified URI.
  384.      *
  385.      * @param  propnode   The DOMNode to check
  386.      * @param  parentnode The parent node of the property. If it is a subproperty,
  387.      *                    this is the parent property node. If it is not, this is the
  388.      *                    microformat node.
  389.      * @return A string with the fully qualified URI.
  390.      */
  391.     uriGetter: function(propnode, parentnode) {
  392.       var pairs = {"a":"href", "img":"src", "object":"data", "area":"href"};
  393.       var name = propnode.nodeName.toLowerCase();
  394.       if (pairs.hasOwnProperty(name)) {
  395.         return propnode[pairs[name]];
  396.       }
  397.       return Microformats.parser.textGetter(propnode, parentnode);
  398.     },
  399.     /**
  400.      * Used to specifically retrieve a telephone number in a microformat node.
  401.      * Basically this is to handle the face that telephone numbers use value
  402.      * as the name as one of their subproperties, but value is also used for
  403.      * value excerpting (http://microformats.org/wiki/hcard#Value_excerpting)
  404.      
  405.      * @param  propnode   The DOMNode to check
  406.      * @param  parentnode The parent node of the property. If it is a subproperty,
  407.      *                    this is the parent property node. If it is not, this is the
  408.      *                    microformat node.
  409.      * @return A string with the telephone number
  410.      */
  411.     telGetter: function(propnode, parentnode) {
  412.       var pairs = {"a":"href", "object":"data", "area":"href"};
  413.       var name = propnode.nodeName.toLowerCase();
  414.       if (pairs.hasOwnProperty(name)) {
  415.         var protocol;
  416.         if (propnode[pairs[name]].indexOf("tel:") == 0) {
  417.           protocol = "tel:";
  418.         }
  419.         if (propnode[pairs[name]].indexOf("fax:") == 0) {
  420.           protocol = "fax:";
  421.         }
  422.         if (propnode[pairs[name]].indexOf("modem:") == 0) {
  423.           protocol = "modem:";
  424.         }
  425.         if (protocol) {
  426.           if (propnode[pairs[name]].indexOf('?') > 0) {
  427.             return unescape(propnode[pairs[name]].substring(protocol.length, propnode[pairs[name]].indexOf('?')));
  428.           } else {
  429.             return unescape(propnode[pairs[name]].substring(protocol.length));
  430.           }
  431.         }
  432.       }
  433.      /* Special case - if this node is a value, use the parent node to get all the values */
  434.       if (Microformats.matchClass(propnode, "value")) {
  435.         return Microformats.parser.textGetter(parentnode, parentnode);
  436.       } else {
  437.         return Microformats.parser.textGetter(propnode, parentnode);
  438.       }
  439.     },
  440.     /**
  441.      * Used to specifically retrieve an email address in a microformat node.
  442.      * This includes at an href, as well as removing subject if specified and
  443.      * the mailto prefix.
  444.      *
  445.      * @param  propnode   The DOMNode to check
  446.      * @param  parentnode The parent node of the property. If it is a subproperty,
  447.      *                    this is the parent property node. If it is not, this is the
  448.      *                    microformat node.
  449.      * @return A string with the email address.
  450.      */
  451.     emailGetter: function(propnode, parentnode) {
  452.       if ((propnode.nodeName.toLowerCase() == "a") || (propnode.nodeName.toLowerCase() == "area")) {
  453.         var mailto = propnode.href;
  454.         /* IO Service won't fully parse mailto, so we do it manually */
  455.         if (mailto.indexOf('?') > 0) {
  456.           return unescape(mailto.substring("mailto:".length, mailto.indexOf('?')));
  457.         } else {
  458.           return unescape(mailto.substring("mailto:".length));
  459.         }
  460.       } else {
  461.         /* Special case - if this node is a value, use the parent node to get all the values */
  462.         /* If this case gets executed, per the value design pattern, the result */
  463.         /* will be the EXACT email address with no extra parsing required */
  464.         if (Microformats.matchClass(propnode, "value")) {
  465.           return Microformats.parser.textGetter(parentnode, parentnode);
  466.         } else {
  467.           return Microformats.parser.textGetter(propnode, parentnode);
  468.         }
  469.       }
  470.     },
  471.     /**
  472.      * Used when a caller needs the text inside a particular DOM node.
  473.      * It calls defaultGetter to handle all the subtleties of getting
  474.      * text from a microformat.
  475.      *
  476.      * @param  propnode   The DOMNode to check
  477.      * @param  parentnode The parent node of the property. If it is a subproperty,
  478.      *                    this is the parent property node. If it is not, this is the
  479.      *                    microformat node.
  480.      * @return A string with just the text including all tags.
  481.      */
  482.     textGetter: function(propnode, parentnode) {
  483.       return Microformats.parser.defaultGetter(propnode, parentnode, "text");
  484.     },
  485.     /**
  486.      * Used when a caller needs the HTML inside a particular DOM node.
  487.      *
  488.      * @param  propnode   The DOMNode to check
  489.      * @param  parentnode The parent node of the property. If it is a subproperty,
  490.      *                    this is the parent property node. If it is not, this is the
  491.      *                    microformat node.
  492.      * @return An emulated string object that also has a new function called toHTML
  493.      */
  494.     HTMLGetter: function(propnode, parentnode) {
  495.       /* This is so we can have a string that behaves like a string */
  496.       /* but also has a new function that can return the HTML that corresponds */
  497.       /* to the string. */
  498.       function mfHTML(value) {
  499.         this.valueOf = function() {return value.valueOf();}
  500.         this.toString = function() {return value.toString();}
  501.       }
  502.       mfHTML.prototype = new String;
  503.       mfHTML.prototype.toHTML = function() {
  504.         return Microformats.parser.defaultGetter(propnode, parentnode, "HTML");
  505.       }
  506.       return new mfHTML(Microformats.parser.defaultGetter(propnode, parentnode, "text"));
  507.     },
  508.     /**
  509.      * Internal parser API used to determine which getter to call based on the
  510.      * datatype specified in the microformat definition.
  511.      *
  512.      * @param  prop       The microformat property in the definition
  513.      * @param  propnode   The DOMNode to check
  514.      * @param  parentnode The parent node of the property. If it is a subproperty,
  515.      *                    this is the parent property node. If it is not, this is the
  516.      *                    microformat node.
  517.      * @return A string with the property value.
  518.      */
  519.     datatypeHelper: function(prop, node, parentnode) {
  520.       var result;
  521.       var datatype = prop.datatype;
  522.       if (prop.implied) {
  523.         datatype = prop.subproperties[prop.implied].datatype;
  524.       }
  525.       switch (datatype) {
  526.         case "dateTime":
  527.           result = Microformats.parser.dateTimeGetter(node, parentnode);
  528.           break;
  529.         case "anyURI":
  530.           result = Microformats.parser.uriGetter(node, parentnode);
  531.           break;
  532.         case "email":
  533.           result = Microformats.parser.emailGetter(node, parentnode);
  534.           break;
  535.         case "tel":
  536.           result = Microformats.parser.telGetter(node, parentnode);
  537.           break;
  538.         case "HTML":
  539.           result = Microformats.parser.HTMLGetter(node, parentnode);
  540.           break;
  541.         case "float":
  542.           var asText = Microformats.parser.textGetter(node, parentnode);
  543.           if (!isNaN(asText)) {
  544.             result = parseFloat(asText);
  545.           }
  546.           break;
  547.         case "custom":
  548.           result = prop.customGetter(node, parentnode);
  549.           break;
  550.         case "microformat":
  551.           try {
  552.             result = new Microformats[prop.microformat].mfObject(node);
  553.           } catch (ex) {
  554.             /* We can swallow this exception. If the creation of the */
  555.             /* mf object fails, then the node isn't a microformat */
  556.           }
  557.           if (result != undefined) {
  558.             if (prop.microformat_property) {
  559.               result = result[prop.microformat_property];
  560.             }
  561.             break;
  562.           }
  563.         default:
  564.           result = Microformats.parser.textGetter(node, parentnode);
  565.           break;
  566.       }
  567.       /* This handles the case where one property implies another property */
  568.       /* For instance, org by itself is actually org.organization-name */
  569.       if (prop.implied && (result != undefined)) {
  570.         var temp = result;
  571.         result = {};
  572.         result[prop.implied] = temp;
  573.       }
  574.       if (prop.values && (result != undefined)) {
  575.         var validType = false;
  576.         for (let value in prop.values) {
  577.           if (result.toLowerCase() == prop.values[value]) {
  578.             validType = true;
  579.             break;
  580.           }
  581.         }
  582.         if (!validType) {
  583.           return;
  584.         }
  585.       }
  586.       return result;
  587.     },
  588.     newMicroformat: function(object, in_node, microformat, validate) {
  589.       /* check to see if we are even valid */
  590.       if (!Microformats[microformat]) {
  591.         throw("Invalid microformat - " + microformat);
  592.       }
  593.       if (in_node.ownerDocument) {
  594.         if (Microformats[microformat].attributeName) {
  595.           if (!(in_node.getAttribute(Microformats[microformat].attributeName))) {
  596.             throw("Node is not a microformat (" + microformat + ")");
  597.           }
  598.         } else {
  599.           if (!Microformats.matchClass(in_node, Microformats[microformat].className)) {
  600.             throw("Node is not a microformat (" + microformat + ")");
  601.           }
  602.         }
  603.       }
  604.       var node = in_node;
  605.       if ((Microformats[microformat].className) && in_node.ownerDocument) {
  606.         node = Microformats.parser.preProcessMicroformat(in_node);
  607.       }
  608.  
  609.       for (let i in Microformats[microformat].properties) {
  610.         object.__defineGetter__(i, Microformats.parser.getMicroformatPropertyGenerator(node, microformat, i, object));
  611.       }
  612.       
  613.       /* The node in the object should be the original node */
  614.       object.node = in_node;
  615.       /* we also store the node that has been "resolved" */
  616.       object.resolvedNode = node; 
  617.       object.semanticType = microformat;
  618.       if (validate) {
  619.         Microformats.parser.validate(node, microformat);
  620.       }
  621.     },
  622.     getMicroformatPropertyGenerator: function getMicroformatPropertyGenerator(node, name, property, microformat)
  623.     {
  624.       return function() {
  625.         var result = Microformats.parser.getMicroformatProperty(node, name, property);
  626. //        delete microformat[property];
  627. //        microformat[property] = result; 
  628.         return result;
  629.       };
  630.     },
  631.     getPropertyInternal: function getPropertyInternal(propnode, parentnode, propobj, propname, mfnode) {
  632.       var result;
  633.       if (propobj.subproperties) {
  634.         for (let subpropname in propobj.subproperties) {
  635.           var subpropnodes;
  636.           var subpropobj = propobj.subproperties[subpropname];
  637.           if (subpropobj.rel == true) {
  638.             subpropnodes = Microformats.getElementsByAttribute(propnode, "rel", subpropname);
  639.           } else {
  640.             subpropnodes = Microformats.getElementsByClassName(propnode, subpropname);
  641.           }
  642.           var resultArray = [];
  643.           var subresult;
  644.           for (let i = 0; i < subpropnodes.length; i++) {
  645.             subresult = Microformats.parser.getPropertyInternal(subpropnodes[i], propnode,
  646.                                                                 subpropobj,
  647.                                                                 subpropname, mfnode);
  648.             if (subresult != undefined) {
  649.               resultArray.push(subresult);
  650.               /* If we're not a plural property, don't bother getting more */
  651.               if (!subpropobj.plural) {
  652.                 break;
  653.               }
  654.             }
  655.           }
  656.           if (resultArray.length == 0) {
  657.             subresult = Microformats.parser.getPropertyInternal(propnode, null,
  658.                                                                 subpropobj,
  659.                                                                 subpropname, mfnode);
  660.             if (subresult != undefined) {
  661.               resultArray.push(subresult);
  662.             }
  663.           }
  664.           if (resultArray.length > 0) {
  665.             result = result || {};
  666.             if (subpropobj.plural) {
  667.               result[subpropname] = resultArray;
  668.             } else {
  669.               result[subpropname] = resultArray[0];
  670.             }
  671.           }
  672.         }
  673.       }
  674.       if (!parentnode || (!result && propobj.subproperties)) {
  675.         if (propobj.virtual) {
  676.           if (propobj.virtualGetter) {
  677.             result = propobj.virtualGetter(mfnode || propnode);
  678.           } else {
  679.             result = Microformats.parser.datatypeHelper(propobj, propnode);
  680.           }
  681.         } else if (propobj.implied) {
  682.           result = Microformats.parser.datatypeHelper(propobj, propnode);
  683.         }
  684.       } else if (!result) {
  685.         result = Microformats.parser.datatypeHelper(propobj, propnode, parentnode);
  686.       }
  687.       return result;
  688.     },
  689.     getMicroformatProperty: function getMicroformatProperty(in_mfnode, mfname, propname) {
  690.       var mfnode = in_mfnode;
  691.       /* If the node has not been preprocessed, the requested microformat */
  692.       /* is a class based microformat and the passed in node is not the */
  693.       /* entire document, preprocess it. Preprocessing the node involves */
  694.       /* creating a duplicate of the node and taking care of things like */
  695.       /* the include and header design patterns */
  696.       if (!in_mfnode.origNode && Microformats[mfname].className && in_mfnode.ownerDocument) {
  697.         mfnode = Microformats.parser.preProcessMicroformat(in_mfnode);
  698.       }
  699.       /* propobj is the corresponding property object in the microformat */
  700.       var propobj;
  701.       /* If there is a corresponding property in the microformat, use it */
  702.       if (Microformats[mfname].properties[propname]) {
  703.         propobj = Microformats[mfname].properties[propname];
  704.       } else {
  705.         /* If we didn't get a property, bail */
  706.         return;
  707.       }
  708.       /* Query the correct set of nodes (rel or class) based on the setting */
  709.       /* in the property */
  710.       var propnodes;
  711.       if (propobj.rel == true) {
  712.         propnodes = Microformats.getElementsByAttribute(mfnode, "rel", propname);
  713.       } else {
  714.         propnodes = Microformats.getElementsByClassName(mfnode, propname);
  715.       }
  716.       for (let i=propnodes.length-1; i >= 0; i--) {
  717.         /* The reason getParent is not used here is because this code does */
  718.         /* not apply to attribute based microformats, plus adr and geo */
  719.         /* when contained in hCard are a special case */
  720.         var parentnode;
  721.         var node = propnodes[i];
  722.         var xpathExpression = "";
  723.         for (let j=0; j < Microformats.list.length; j++) {
  724.           /* Don't treat adr or geo in an hCard as a microformat in this case */
  725.           if ((mfname == "hCard") && ((Microformats.list[j] == "adr") || (Microformats.list[j] == "geo"))) {
  726.             continue;
  727.           }
  728.           if (Microformats[Microformats.list[j]].className) {
  729.             if (xpathExpression.length == 0) {
  730.               xpathExpression = "ancestor::*[";
  731.             } else {
  732.               xpathExpression += " or ";
  733.             }
  734.             xpathExpression += "contains(concat(' ', @class, ' '), ' " + Microformats[Microformats.list[j]].className + " ')";
  735.           }
  736.         }
  737.         xpathExpression += "][1]";
  738.         var xpathResult = (node.ownerDocument || node).evaluate(xpathExpression, node, null,  Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null);
  739.         if (xpathResult.singleNodeValue) {
  740.           xpathResult.singleNodeValue.microformat = mfname;
  741.           parentnode = xpathResult.singleNodeValue;
  742.         }
  743.         /* If the propnode is not a child of the microformat, and */
  744.         /* the property belongs to the parent microformat as well, */
  745.         /* remove it. */
  746.         if (parentnode != mfnode) {
  747.           var mfNameString = Microformats.getNamesFromNode(parentnode);
  748.           var mfNames = mfNameString.split(" ");
  749.           var j;
  750.           for (j=0; j < mfNames.length; j++) {
  751.             /* If this property is in the parent microformat, remove the node  */
  752.             if (Microformats[mfNames[j]].properties[propname]) {
  753.               propnodes.splice(i,1);;
  754.               break;
  755.             }
  756.           }
  757.         }
  758.       }
  759.       if (propnodes.length > 0) {
  760.         var resultArray = [];
  761.         for (let i = 0; i < propnodes.length; i++) {
  762.           var subresult = Microformats.parser.getPropertyInternal(propnodes[i],
  763.                                                                   mfnode,
  764.                                                                   propobj,
  765.                                                                   propname);
  766.           if (subresult != undefined) {
  767.             resultArray.push(subresult);
  768.             /* If we're not a plural property, don't bother getting more */
  769.             if (!propobj.plural) {
  770.               return resultArray[0];
  771.             }
  772.           }
  773.         }
  774.         if (resultArray.length > 0) {
  775.           return resultArray;
  776.         }
  777.       } else {
  778.         /* If we didn't find any class nodes, check to see if this property */
  779.         /* is virtual and if so, call getPropertyInternal again */
  780.         if (propobj.virtual) {
  781.           return Microformats.parser.getPropertyInternal(mfnode, null,
  782.                                                          propobj, propname);
  783.         }
  784.       }
  785.       return;
  786.     },
  787.     /**
  788.      * Internal parser API used to resolve includes and headers. Includes are
  789.      * resolved by simply cloning the node and replacing it in a clone of the
  790.      * original DOM node. Headers are resolved by creating a span and then copying
  791.      * the innerHTML and the class name.
  792.      *
  793.      * @param  in_mfnode The node to preProcess.
  794.      * @return If the node had includes or headers, a cloned node otherwise
  795.      *         the original node. You can check to see if the node was cloned
  796.      *         by looking for .origNode in the new node.
  797.      */
  798.     preProcessMicroformat: function preProcessMicroformat(in_mfnode) {
  799.       var mfnode;
  800.       var includes = Microformats.getElementsByClassName(in_mfnode, "include");
  801.       if ((includes.length > 0) || ((in_mfnode.nodeName.toLowerCase() == "td") && (in_mfnode.getAttribute("headers")))) {
  802.         mfnode = in_mfnode.cloneNode(true);
  803.         mfnode.origNode = in_mfnode;
  804.         if (includes.length > 0) {
  805.           includes = Microformats.getElementsByClassName(mfnode, "include");
  806.           var includeId;
  807.           var include_length = includes.length;
  808.           for (let i = include_length -1; i >= 0; i--) {
  809.             if (includes[i].nodeName.toLowerCase() == "a") {
  810.               includeId = includes[i].getAttribute("href").substr(1);
  811.             }
  812.             if (includes[i].nodeName.toLowerCase() == "object") {
  813.               includeId = includes[i].getAttribute("data").substr(1);
  814.             }
  815.             if (in_mfnode.ownerDocument.getElementById(includeId)) {
  816.               includes[i].parentNode.replaceChild(in_mfnode.ownerDocument.getElementById(includeId).cloneNode(true), includes[i]);
  817.             }
  818.           }
  819.         } else {
  820.           var headers = in_mfnode.getAttribute("headers").split(" ");
  821.           for (let i = 0; i < headers.length; i++) {
  822.             var tempNode = in_mfnode.ownerDocument.createElement("span");
  823.             var headerNode = in_mfnode.ownerDocument.getElementById(headers[i]);
  824.             if (headerNode) {
  825.               tempNode.innerHTML = headerNode.innerHTML;
  826.               tempNode.className = headerNode.className;
  827.               mfnode.appendChild(tempNode);
  828.             }
  829.           }
  830.         }
  831.       } else {
  832.         mfnode = in_mfnode;
  833.       }
  834.       return mfnode;
  835.     },
  836.     validate: function validate(mfnode, mfname) {
  837.       var error = "";
  838.       if (Microformats[mfname].validate) {
  839.         return Microformats[mfname].validate(mfnode);
  840.       } else if (Microformats[mfname].required) {
  841.         for (let i=0;i<Microformats[mfname].required.length;i++) {
  842.           if (!Microformats.parser.getMicroformatProperty(mfnode, mfname, Microformats[mfname].required[i])) {
  843.             error += "Required property " + Microformats[mfname].required[i] + " not specified\n";
  844.           }
  845.         }
  846.         if (error.length > 0) {
  847.           throw(error);
  848.         }
  849.         return true;
  850.       }
  851.     },
  852.     /* This function normalizes an ISO8601 date by adding punctuation and */
  853.     /* ensuring that hours and seconds have values */
  854.     normalizeISO8601: function normalizeISO8601(string)
  855.     {
  856.       var dateArray = string.match(/(\d\d\d\d)(?:-?(\d\d)(?:-?(\d\d)(?:[T ](\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(?:([-+Z])(?:(\d\d)(?::?(\d\d))?)?)?)?)?)?/);
  857.   
  858.       var dateString;
  859.       var tzOffset = 0;
  860.       if (!dateArray) {
  861.         return;
  862.       }
  863.       if (dateArray[1]) {
  864.         dateString = dateArray[1];
  865.         if (dateArray[2]) {
  866.           dateString += "-" + dateArray[2];
  867.           if (dateArray[3]) {
  868.             dateString += "-" + dateArray[3];
  869.             if (dateArray[4]) {
  870.               dateString += "T" + dateArray[4];
  871.               if (dateArray[5]) {
  872.                 dateString += ":" + dateArray[5];
  873.               } else {
  874.                 dateString += ":" + "00";
  875.               }
  876.               if (dateArray[6]) {
  877.                 dateString += ":" + dateArray[6];
  878.               } else {
  879.                 dateString += ":" + "00";
  880.               }
  881.               if (dateArray[7]) {
  882.                 dateString += "." + dateArray[7];
  883.               }
  884.               if (dateArray[8]) {
  885.                 dateString += dateArray[8];
  886.                 if ((dateArray[8] == "+") || (dateArray[8] == "-")) {
  887.                   if (dateArray[9]) {
  888.                     dateString += dateArray[9];
  889.                     if (dateArray[10]) {
  890.                       dateString += dateArray[10];
  891.                     }
  892.                   }
  893.                 }
  894.               }
  895.             }
  896.           }
  897.         }
  898.       }
  899.       return dateString;
  900.     }
  901.   },
  902.   /**
  903.    * Converts an ISO8601 date into a JavaScript date object, honoring the TZ
  904.    * offset and Z if present to convert the date to local time
  905.    * NOTE: I'm using an extra parameter on the date object for this function.
  906.    * I set date.time to true if there is a date, otherwise date.time is false.
  907.    * 
  908.    * @param  string ISO8601 formatted date
  909.    * @return JavaScript date object that represents the ISO date. 
  910.    */
  911.   dateFromISO8601: function dateFromISO8601(string) {
  912.     var dateArray = string.match(/(\d\d\d\d)(?:-?(\d\d)(?:-?(\d\d)(?:[T ](\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(?:([-+Z])(?:(\d\d)(?::?(\d\d))?)?)?)?)?)?/);
  913.   
  914.     var date = new Date(dateArray[1], 0, 1);
  915.     date.time = false;
  916.  
  917.     if (dateArray[2]) {
  918.       date.setMonth(dateArray[2] - 1);
  919.     }
  920.     if (dateArray[3]) {
  921.       date.setDate(dateArray[3]);
  922.     }
  923.     if (dateArray[4]) {
  924.       date.setHours(dateArray[4]);
  925.       date.time = true;
  926.       if (dateArray[5]) {
  927.         date.setMinutes(dateArray[5]);
  928.         if (dateArray[6]) {
  929.           date.setSeconds(dateArray[6]);
  930.           if (dateArray[7]) {
  931.             date.setMilliseconds(Number("0." + dateArray[7]) * 1000);
  932.           }
  933.         }
  934.       }
  935.     }
  936.     if (dateArray[8]) {
  937.       if (dateArray[8] == "-") {
  938.         if (dateArray[9] && dateArray[10]) {
  939.           date.setHours(date.getHours() + parseInt(dateArray[9], 10));
  940.           date.setMinutes(date.getMinutes() + parseInt(dateArray[10], 10));
  941.         }
  942.       } else if (dateArray[8] == "+") {
  943.         if (dateArray[9] && dateArray[10]) {
  944.           date.setHours(date.getHours() - parseInt(dateArray[9], 10));
  945.           date.setMinutes(date.getMinutes() - parseInt(dateArray[10], 10));
  946.         }
  947.       }
  948.       /* at this point we have the time in gmt */
  949.       /* convert to local if we had a Z - or + */
  950.       if (dateArray[8]) {
  951.         var tzOffset = date.getTimezoneOffset();
  952.         if (tzOffset < 0) {
  953.           date.setMinutes(date.getMinutes() + tzOffset); 
  954.         } else if (tzOffset > 0) {
  955.           date.setMinutes(date.getMinutes() - tzOffset); 
  956.         }
  957.       }
  958.     }
  959.     return date;
  960.   },
  961.   /**
  962.    * Converts a Javascript date object into an ISO 8601 formatted date
  963.    * NOTE: I'm using an extra parameter on the date object for this function.
  964.    * If date.time is NOT true, this function only outputs the date.
  965.    * 
  966.    * @param  date        Javascript Date object
  967.    * @param  punctuation true if the date should have -/:
  968.    * @return string with the ISO date. 
  969.    */
  970.   iso8601FromDate: function iso8601FromDate(date, punctuation) {
  971.     var string = date.getFullYear().toString();
  972.     if (punctuation) {
  973.       string += "-";
  974.     }
  975.     string += (date.getMonth() + 1).toString().replace(/\b(\d)\b/g, '0$1');
  976.     if (punctuation) {
  977.       string += "-";
  978.     }
  979.     string += date.getDate().toString().replace(/\b(\d)\b/g, '0$1');
  980.     if (date.time) {
  981.       string += "T";
  982.       string += date.getHours().toString().replace(/\b(\d)\b/g, '0$1');
  983.       if (punctuation) {
  984.         string += ":";
  985.       }
  986.       string += date.getMinutes().toString().replace(/\b(\d)\b/g, '0$1');
  987.       if (punctuation) {
  988.         string += ":";
  989.       }
  990.       string += date.getSeconds().toString().replace(/\b(\d)\b/g, '0$1');
  991.       if (date.getMilliseconds() > 0) {
  992.         if (punctuation) {
  993.           string += ".";
  994.         }
  995.         string += date.getMilliseconds().toString();
  996.       }
  997.     }
  998.     return string;
  999.   },
  1000.   simpleEscape: function simpleEscape(s)
  1001.   {
  1002.     s = s.replace(/\&/g, '%26');
  1003.     s = s.replace(/\#/g, '%23');
  1004.     s = s.replace(/\+/g, '%2B');
  1005.     s = s.replace(/\-/g, '%2D');
  1006.     s = s.replace(/\=/g, '%3D');
  1007.     s = s.replace(/\'/g, '%27');
  1008.     s = s.replace(/\,/g, '%2C');
  1009. //    s = s.replace(/\r/g, '%0D');
  1010. //    s = s.replace(/\n/g, '%0A');
  1011.     s = s.replace(/ /g, '+');
  1012.     return s;
  1013.   },
  1014.   /**
  1015.    * Not intended for external consumption. Microformat implementations might use it.
  1016.    *
  1017.    * Retrieve elements matching all classes listed in a space-separated string.
  1018.    * I had to implement my own because I need an Array, not an nsIDomNodeList
  1019.    * 
  1020.    * @param  rootElement      The DOM element at which to start searching (optional)
  1021.    * @param  className        A space separated list of classenames
  1022.    * @return microformatNodes An array of DOM Nodes, each representing a
  1023.                               microformat in the document.
  1024.    */
  1025.   getElementsByClassName: function getElementsByClassName(rootNode, className)
  1026.   {
  1027.     var returnElements = [];
  1028.  
  1029.     if ((rootNode.ownerDocument || rootNode).getElementsByClassName) {
  1030.     /* Firefox 3 - native getElementsByClassName */
  1031.       var col = rootNode.getElementsByClassName(className);
  1032.       for (let i = 0; i < col.length; i++) {
  1033.         returnElements[i] = col[i];
  1034.       }
  1035.     } else if ((rootNode.ownerDocument || rootNode).evaluate) {
  1036.     /* Firefox 2 and below - XPath */
  1037.       var xpathExpression;
  1038.       xpathExpression = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
  1039.       var xpathResult = (rootNode.ownerDocument || rootNode).evaluate(xpathExpression, rootNode, null, 0, null);
  1040.  
  1041.       var node;
  1042.       while (node = xpathResult.iterateNext()) {
  1043.         returnElements.push(node);
  1044.       }
  1045.     } else {
  1046.     /* Slow fallback for testing */
  1047.       className = className.replace(/\-/g, "\\-");
  1048.       var elements = rootNode.getElementsByTagName("*");
  1049.       for (let i=0;i<elements.length;i++) {
  1050.         if (elements[i].className.match("(^|\\s)" + className + "(\\s|$)")) {
  1051.           returnElements.push(elements[i]);
  1052.         }
  1053.       }
  1054.     }
  1055.     return returnElements;
  1056.   },
  1057.   /**
  1058.    * Not intended for external consumption. Microformat implementations might use it.
  1059.    *
  1060.    * Retrieve elements matching an attribute and an attribute list in a space-separated string.
  1061.    * 
  1062.    * @param  rootElement      The DOM element at which to start searching (optional)
  1063.    * @param  atributeName     The attribute name to match against
  1064.    * @param  attributeValues  A space separated list of attribute values
  1065.    * @return microformatNodes An array of DOM Nodes, each representing a
  1066.                               microformat in the document.
  1067.    */
  1068.   getElementsByAttribute: function getElementsByAttribute(rootNode, attributeName, attributeValues)
  1069.   {
  1070.     var attributeList = attributeValues.split(" ");
  1071.  
  1072.     var returnElements = [];
  1073.  
  1074.     if ((rootNode.ownerDocument || rootNode).evaluate) {
  1075.     /* Firefox 3 and below - XPath */
  1076.       /* Create an XPath expression based on the attribute list */
  1077.       var xpathExpression = ".//*[";
  1078.       for (let i = 0; i < attributeList.length; i++) {
  1079.         if (i != 0) {
  1080.           xpathExpression += " or ";
  1081.         }
  1082.         xpathExpression += "contains(concat(' ', @" + attributeName + ", ' '), ' " + attributeList[i] + " ')";
  1083.       }
  1084.       xpathExpression += "]"; 
  1085.  
  1086.       var xpathResult = (rootNode.ownerDocument || rootNode).evaluate(xpathExpression, rootNode, null, 0, null);
  1087.  
  1088.       var node;
  1089.       while (node = xpathResult.iterateNext()) {
  1090.         returnElements.push(node);
  1091.       }
  1092.     } else {
  1093.     /* Need Slow fallback for testing */
  1094.     }
  1095.     return returnElements;
  1096.   },
  1097.   matchClass: function matchClass(node, className) {
  1098.     var classValue = node.getAttribute("class");
  1099.     return (classValue && classValue.match("(^|\\s)" + className + "(\\s|$)"));
  1100.   }
  1101. };
  1102.  
  1103. /* MICROFORMAT DEFINITIONS BEGIN HERE */
  1104.  
  1105. function adr(node, validate) {
  1106.   if (node) {
  1107.     Microformats.parser.newMicroformat(this, node, "adr", validate);
  1108.   }
  1109. }
  1110.  
  1111. adr.prototype.toString = function() {
  1112.   var address_text = "";
  1113.   var start_parens = false;
  1114.   if (this["street-address"]) {
  1115.     address_text += this["street-address"][0];
  1116.   } else if (this["extended-address"]) {
  1117.     address_text += this["extended-address"];
  1118.   }
  1119.   if (this["locality"]) {
  1120.     if (this["street-address"] || this["extended-address"]) {
  1121.       address_text += " (";
  1122.       start_parens = true;
  1123.     }
  1124.     address_text += this["locality"];
  1125.   }
  1126.   if (this["region"]) {
  1127.     if ((this["street-address"] || this["extended-address"]) && (!start_parens)) {
  1128.       address_text += " (";
  1129.       start_parens = true;
  1130.     } else if (this["locality"]) {
  1131.       address_text += ", ";
  1132.     }
  1133.     address_text += this["region"];
  1134.   }
  1135.   if (this["country-name"]) {
  1136.     if ((this["street-address"] || this["extended-address"]) && (!start_parens)) {
  1137.       address_text += " (";
  1138.       start_parens = true;
  1139.       address_text += this["country-name"];
  1140.     } else if ((!this["locality"]) && (!this["region"])) {
  1141.       address_text += this["country-name"];
  1142.     } else if (((!this["locality"]) && (this["region"])) || ((this["locality"]) && (!this["region"]))) {
  1143.       address_text += ", ";
  1144.       address_text += this["country-name"];
  1145.     }
  1146.   }
  1147.   if (start_parens) {
  1148.     address_text += ")";
  1149.   }
  1150.   return address_text;
  1151. }
  1152.  
  1153. var adr_definition = {
  1154.   mfObject: adr,
  1155.   className: "adr",
  1156.   properties: {
  1157.     "type" : {
  1158.       plural: true,
  1159.       values: ["work", "home", "pref", "postal", "dom", "intl", "parcel"]
  1160.     },
  1161.     "post-office-box" : {
  1162.     },
  1163.     "street-address" : {
  1164.       plural: true
  1165.     },
  1166.     "extended-address" : {
  1167.     },
  1168.     "locality" : {
  1169.     },
  1170.     "region" : {
  1171.     },
  1172.     "postal-code" : {
  1173.     },
  1174.     "country-name" : {
  1175.     }
  1176.   },
  1177.   validate: function(node) {
  1178.     var xpathExpression = "count(descendant::*[" +
  1179.                                               "contains(concat(' ', @class, ' '), ' post-office-box ')" +
  1180.                                               " or contains(concat(' ', @class, ' '), ' street-address ')" +
  1181.                                               " or contains(concat(' ', @class, ' '), ' extended-address ')" +
  1182.                                               " or contains(concat(' ', @class, ' '), ' locality ')" +
  1183.                                               " or contains(concat(' ', @class, ' '), ' region ')" +
  1184.                                               " or contains(concat(' ', @class, ' '), ' postal-code ')" +
  1185.                                               " or contains(concat(' ', @class, ' '), ' country-name')" +
  1186.                                               "])";
  1187.     var xpathResult = (node.ownerDocument || node).evaluate(xpathExpression, node, null,  Components.interfaces.nsIDOMXPathResult.ANY_TYPE, null).numberValue;
  1188.     if (xpathResult == 0) {
  1189.       throw("Unable to create microformat");
  1190.     }
  1191.     return true;
  1192.   }
  1193. };
  1194.  
  1195. Microformats.add("adr", adr_definition);
  1196.  
  1197. function hCard(node, validate) {
  1198.   if (node) {
  1199.     Microformats.parser.newMicroformat(this, node, "hCard", validate);
  1200.   }
  1201. }
  1202. hCard.prototype.toString = function() {
  1203.   if (this.resolvedNode) {
  1204.     /* If this microformat has an include pattern, put the */
  1205.     /* organization-name in parenthesis after the fn to differentiate */
  1206.     /* them. */
  1207.     var fns = Microformats.getElementsByClassName(this.node, "fn");
  1208.     if (fns.length === 0) {
  1209.       if (this.fn) {
  1210.         if (this.org && this.org[0]["organization-name"] && (this.fn != this.org[0]["organization-name"])) {
  1211.           return this.fn + " (" + this.org[0]["organization-name"] + ")";
  1212.         }
  1213.       }
  1214.     }
  1215.   }
  1216.   return this.fn;
  1217. }
  1218.  
  1219. var hCard_definition = {
  1220.   mfObject: hCard,
  1221.   className: "vcard",
  1222.   required: ["fn"],
  1223.   properties: {
  1224.     "adr" : {
  1225.       plural: true,
  1226.       datatype: "microformat",
  1227.       microformat: "adr"
  1228.     },
  1229.     "agent" : {
  1230.       plural: true,
  1231.       datatype: "microformat",
  1232.       microformat: "hCard"
  1233.     },
  1234.     "bday" : {
  1235.       datatype: "dateTime"
  1236.     },
  1237.     "class" : {
  1238.     },
  1239.     "category" : {
  1240.       plural: true,
  1241.       datatype: "microformat",
  1242.       microformat: "tag",
  1243.       microformat_property: "tag"
  1244.     },
  1245.     "email" : {
  1246.       subproperties: {
  1247.         "type" : {
  1248.           plural: true,
  1249.           values: ["internet", "x400", "pref"]
  1250.         },
  1251.         "value" : {
  1252.           datatype: "email",
  1253.           virtual: true
  1254.         }
  1255.       },
  1256.       plural: true   
  1257.     },
  1258.     "fn" : {
  1259.       required: true
  1260.     },
  1261.     "geo" : {
  1262.       datatype: "microformat",
  1263.       microformat: "geo"
  1264.     },
  1265.     "key" : {
  1266.       plural: true
  1267.     },
  1268.     "label" : {
  1269.       plural: true
  1270.     },
  1271.     "logo" : {
  1272.       plural: true,
  1273.       datatype: "anyURI"
  1274.     },
  1275.     "mailer" : {
  1276.       plural: true
  1277.     },
  1278.     "n" : {
  1279.       subproperties: {
  1280.         "honorific-prefix" : {
  1281.           plural: true
  1282.         },
  1283.         "given-name" : {
  1284.           plural: true
  1285.         },
  1286.         "additional-name" : {
  1287.           plural: true
  1288.         },
  1289.         "family-name" : {
  1290.           plural: true
  1291.         },
  1292.         "honorific-suffix" : {
  1293.           plural: true
  1294.         }
  1295.       },
  1296.       virtual: true,
  1297.       /*  Implied "n" Optimization */
  1298.       /* http://microformats.org/wiki/hcard#Implied_.22n.22_Optimization */
  1299.       virtualGetter: function(mfnode) {
  1300.         var fn = Microformats.parser.getMicroformatProperty(mfnode, "hCard", "fn");
  1301.         var orgs = Microformats.parser.getMicroformatProperty(mfnode, "hCard", "org");
  1302.         var given_name = [];
  1303.         var family_name = [];
  1304.         if (fn && (!orgs || (orgs.length > 1) || (fn != orgs[0]["organization-name"]))) {
  1305.           var fns = fn.split(" ");
  1306.           if (fns.length === 2) {
  1307.             if (fns[0].charAt(fns[0].length-1) == ',') {
  1308.               given_name[0] = fns[1];
  1309.               family_name[0] = fns[0].substr(0, fns[0].length-1);
  1310.             } else if (fns[1].length == 1) {
  1311.               given_name[0] = fns[1];
  1312.               family_name[0] = fns[0];
  1313.             } else if ((fns[1].length == 2) && (fns[1].charAt(fns[1].length-1) == '.')) {
  1314.               given_name[0] = fns[1];
  1315.               family_name[0] = fns[0];
  1316.             } else {
  1317.               given_name[0] = fns[0];
  1318.               family_name[0] = fns[1];
  1319.             }
  1320.             return {"given-name" : given_name, "family-name" : family_name};
  1321.           }
  1322.         }
  1323.       }
  1324.     },
  1325.     "nickname" : {
  1326.       plural: true,
  1327.       virtual: true,
  1328.       /* Implied "nickname" Optimization */
  1329.       /* http://microformats.org/wiki/hcard#Implied_.22nickname.22_Optimization */
  1330.       virtualGetter: function(mfnode) {
  1331.         var fn = Microformats.parser.getMicroformatProperty(mfnode, "hCard", "fn");
  1332.         var orgs = Microformats.parser.getMicroformatProperty(mfnode, "hCard", "org");
  1333.         var given_name;
  1334.         var family_name;
  1335.         if (fn && (!orgs || (orgs.length) > 1 || (fn != orgs[0]["organization-name"]))) {
  1336.           var fns = fn.split(" ");
  1337.           if (fns.length === 1) {
  1338.             return [fns[0]];
  1339.           }
  1340.         }
  1341.         return;
  1342.       }
  1343.     },
  1344.     "note" : {
  1345.       plural: true,
  1346.       datatype: "HTML"
  1347.     },
  1348.     "org" : {
  1349.       subproperties: {
  1350.         "organization-name" : {
  1351.         },
  1352.         "organization-unit" : {
  1353.           plural: true
  1354.         }
  1355.       },
  1356.       plural: true,
  1357.       implied: "organization-name"
  1358.     },
  1359.     "photo" : {
  1360.       plural: true,
  1361.       datatype: "anyURI"
  1362.     },
  1363.     "rev" : {
  1364.       datatype: "dateTime"
  1365.     },
  1366.     "role" : {
  1367.       plural: true
  1368.     },
  1369.     "sequence" : {
  1370.     },
  1371.     "sort-string" : {
  1372.     },
  1373.     "sound" : {
  1374.       plural: true
  1375.     },
  1376.     "title" : {
  1377.       plural: true
  1378.     },
  1379.     "tel" : {
  1380.       subproperties: {
  1381.         "type" : {
  1382.           plural: true,
  1383.           values: ["msg", "home", "work", "pref", "voice", "fax", "cell", "video", "pager", "bbs", "car", "isdn", "pcs"]
  1384.         },
  1385.         "value" : {
  1386.           datatype: "tel"
  1387.         }
  1388.       },
  1389.       plural: true,
  1390.       implied: "value"
  1391.     },
  1392.     "tz" : {
  1393.     },
  1394.     "uid" : {
  1395.       datatype: "anyURI"
  1396.     },
  1397.     "url" : {
  1398.       plural: true,
  1399.       datatype: "anyURI"
  1400.     }
  1401.   }
  1402. };
  1403.  
  1404. Microformats.add("hCard", hCard_definition);
  1405.  
  1406. function hCalendar(node, validate) {
  1407.   if (node) {
  1408.     Microformats.parser.newMicroformat(this, node, "hCalendar", validate);
  1409.   }
  1410. }
  1411. hCalendar.prototype.toString = function() {
  1412.   if (this.resolvedNode) {
  1413.     /* If this microformat has an include pattern, put the */
  1414.     /* dtstart in parenthesis after the summary to differentiate */
  1415.     /* them. */
  1416.     var summaries = Microformats.getElementsByClassName(this.node, "summary");
  1417.     if (summaries.length === 0) {
  1418.       if (this.summary) {
  1419.         if (this.dtstart) {
  1420.           return this.summary + " (" + Microformats.dateFromISO8601(this.dtstart).toLocaleString() + ")";
  1421.         }
  1422.       }
  1423.     }
  1424.   }
  1425.   if (this.dtstart) {
  1426.     return this.summary;
  1427.   }
  1428.   return;
  1429. }
  1430.  
  1431. var hCalendar_definition = {
  1432.   mfObject: hCalendar,
  1433.   className: "vevent",
  1434.   required: ["summary", "dtstart"],
  1435.   properties: {
  1436.     "category" : {
  1437.       plural: true,
  1438.       datatype: "microformat",
  1439.       microformat: "tag",
  1440.       microformat_property: "tag"
  1441.     },
  1442.     "class" : {
  1443.       values: ["public", "private", "confidential"]
  1444.     },
  1445.     "description" : {
  1446.       datatype: "HTML"
  1447.     },
  1448.     "dtstart" : {
  1449.       datatype: "dateTime"
  1450.     },
  1451.     "dtend" : {
  1452.       datatype: "dateTime"
  1453.     },
  1454.     "dtstamp" : {
  1455.       datatype: "dateTime"
  1456.     },
  1457.     "duration" : {
  1458.     },
  1459.     "geo" : {
  1460.       datatype: "microformat",
  1461.       microformat: "geo"
  1462.     },
  1463.     "location" : {
  1464.       datatype: "microformat",
  1465.       microformat: "hCard"
  1466.     },
  1467.     "status" : {
  1468.       values: ["tentative", "confirmed", "cancelled"]
  1469.     },
  1470.     "summary" : {},
  1471.     "transp" : {
  1472.       values: ["opaque", "transparent"]
  1473.     },
  1474.     "uid" : {
  1475.       datatype: "anyURI"
  1476.     },
  1477.     "url" : {
  1478.       datatype: "anyURI"
  1479.     },
  1480.     "last-modified" : {
  1481.       datatype: "dateTime"
  1482.     },
  1483.     "rrule" : {
  1484.       subproperties: {
  1485.         "interval" : {
  1486.           virtual: true,
  1487.           /* This will only be called in the virtual case */
  1488.           virtualGetter: function(mfnode) {
  1489.             return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "interval");
  1490.           }
  1491.         },
  1492.         "freq" : {
  1493.           virtual: true,
  1494.           /* This will only be called in the virtual case */
  1495.           virtualGetter: function(mfnode) {
  1496.             return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "freq");
  1497.           }
  1498.         },
  1499.         "bysecond" : {
  1500.           virtual: true,
  1501.           /* This will only be called in the virtual case */
  1502.           virtualGetter: function(mfnode) {
  1503.             return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "bysecond");
  1504.           }
  1505.         },
  1506.         "byminute" : {
  1507.           virtual: true,
  1508.           /* This will only be called in the virtual case */
  1509.           virtualGetter: function(mfnode) {
  1510.             return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "byminute");
  1511.           }
  1512.         },
  1513.         "byhour" : {
  1514.           virtual: true,
  1515.           /* This will only be called in the virtual case */
  1516.           virtualGetter: function(mfnode) {
  1517.             return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "byhour");
  1518.           }
  1519.         },
  1520.         "bymonthday" : {
  1521.           virtual: true,
  1522.           /* This will only be called in the virtual case */
  1523.           virtualGetter: function(mfnode) {
  1524.             return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "bymonthday");
  1525.           }
  1526.         },
  1527.         "byyearday" : {
  1528.           virtual: true,
  1529.           /* This will only be called in the virtual case */
  1530.           virtualGetter: function(mfnode) {
  1531.             return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "byyearday");
  1532.           }
  1533.         },
  1534.         "byweekno" : {
  1535.           virtual: true,
  1536.           /* This will only be called in the virtual case */
  1537.           virtualGetter: function(mfnode) {
  1538.             return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "byweekno");
  1539.           }
  1540.         },
  1541.         "bymonth" : {
  1542.           virtual: true,
  1543.           /* This will only be called in the virtual case */
  1544.           virtualGetter: function(mfnode) {
  1545.             return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "bymonth");
  1546.           }
  1547.         },
  1548.         "byday" : {
  1549.           virtual: true,
  1550.           /* This will only be called in the virtual case */
  1551.           virtualGetter: function(mfnode) {
  1552.             return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "byday");
  1553.           }
  1554.         },
  1555.         "until" : {
  1556.           virtual: true,
  1557.           /* This will only be called in the virtual case */
  1558.           virtualGetter: function(mfnode) {
  1559.             return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "until");
  1560.           }
  1561.         },
  1562.         "count" : {
  1563.           virtual: true,
  1564.           /* This will only be called in the virtual case */
  1565.           virtualGetter: function(mfnode) {
  1566.             return Microformats.hCalendar.properties.rrule.retrieve(mfnode, "count");
  1567.           }
  1568.         }
  1569.       },
  1570.       retrieve: function(mfnode, property) {
  1571.         var value = Microformats.parser.textGetter(mfnode);
  1572.         var rrule;
  1573.         rrule = value.split(';');
  1574.         for (let i=0; i < rrule.length; i++) {
  1575.           if (rrule[i].match(property)) {
  1576.             return rrule[i].split('=')[1];
  1577.           }
  1578.         }
  1579.       }
  1580.     }
  1581.   }
  1582. };
  1583.  
  1584. Microformats.add("hCalendar", hCalendar_definition);
  1585.  
  1586. function geo(node, validate) {
  1587.   if (node) {
  1588.     Microformats.parser.newMicroformat(this, node, "geo", validate);
  1589.   }
  1590. }
  1591. geo.prototype.toString = function() {
  1592.   if (this.latitude != undefined) {
  1593.     if (!isFinite(this.latitude) || (this.latitude > 360) || (this.latitude < -360)) {
  1594.       return;
  1595.     }
  1596.   }
  1597.   if (this.longitude != undefined) {
  1598.     if (!isFinite(this.longitude) || (this.longitude > 360) || (this.longitude < -360)) {
  1599.       return;
  1600.     }
  1601.   }
  1602.  
  1603.   if ((this.latitude != undefined) && (this.longitude != undefined)) {
  1604.     var s;
  1605.     if ((this.node.localName.toLowerCase() == "abbr") || (this.node.localName.toLowerCase() == "html:abbr")) {
  1606.       s = this.node.textContent;
  1607.     }
  1608.  
  1609.     if (s) {
  1610.       return s;
  1611.     }
  1612.  
  1613.     /* check if geo is contained in a vcard */
  1614.     var xpathExpression = "ancestor::*[contains(concat(' ', @class, ' '), ' vcard ')]";
  1615.     var xpathResult = this.node.ownerDocument.evaluate(xpathExpression, this.node, null,  Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null);
  1616.     if (xpathResult.singleNodeValue) {
  1617.       var hcard = new hCard(xpathResult.singleNodeValue);
  1618.       if (hcard.fn) {
  1619.         return hcard.fn;
  1620.       }
  1621.     }
  1622.     /* check if geo is contained in a vevent */
  1623.     xpathExpression = "ancestor::*[contains(concat(' ', @class, ' '), ' vevent ')]";
  1624.     xpathResult = this.node.ownerDocument.evaluate(xpathExpression, this.node, null,  Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, xpathResult);
  1625.     if (xpathResult.singleNodeValue) {
  1626.       var hcal = new hCalendar(xpathResult.singleNodeValue);
  1627.       if (hcal.summary) {
  1628.         return hcal.summary;
  1629.       }
  1630.     }
  1631.     if (s) {
  1632.       return s;
  1633.     } else {
  1634.       return this.latitude + ", " + this.longitude;
  1635.     }
  1636.   }
  1637. }
  1638.  
  1639. var geo_definition = {
  1640.   mfObject: geo,
  1641.   className: "geo",
  1642.   required: ["latitude","longitude"],
  1643.   properties: {
  1644.     "latitude" : {
  1645.       datatype: "float",
  1646.       virtual: true,
  1647.       /* This will only be called in the virtual case */
  1648.       virtualGetter: function(mfnode) {
  1649.         var value = Microformats.parser.textGetter(mfnode);
  1650.         var latlong;
  1651.         if (value.match(';')) {
  1652.           latlong = value.split(';');
  1653.           if (latlong[0]) {
  1654.             if (!isNaN(latlong[0])) {
  1655.               return parseFloat(latlong[0]);
  1656.             }
  1657.           }
  1658.         }
  1659.       }
  1660.     },
  1661.     "longitude" : {
  1662.       datatype: "float",
  1663.       virtual: true,
  1664.       /* This will only be called in the virtual case */
  1665.       virtualGetter: function(mfnode) {
  1666.         var value = Microformats.parser.textGetter(mfnode);
  1667.         var latlong;
  1668.         if (value.match(';')) {
  1669.           latlong = value.split(';');
  1670.           if (latlong[1]) {
  1671.             if (!isNaN(latlong[1])) {
  1672.               return parseFloat(latlong[1]);
  1673.             }
  1674.           }
  1675.         }
  1676.       }
  1677.     }
  1678.   },
  1679.   validate: function(node) {
  1680.     var latitude = Microformats.parser.getMicroformatProperty(node, "geo", "latitude");
  1681.     var longitude = Microformats.parser.getMicroformatProperty(node, "geo", "longitude");
  1682.     if (latitude != undefined) {
  1683.       if (!isFinite(latitude) || (latitude > 360) || (latitude < -360)) {
  1684.         throw("Invalid latitude");
  1685.       }
  1686.     } else {
  1687.       throw("No latitude specified");
  1688.     }
  1689.     if (longitude != undefined) {
  1690.       if (!isFinite(longitude) || (longitude > 360) || (longitude < -360)) {
  1691.         throw("Invalid longitude");
  1692.       }
  1693.     } else {
  1694.       throw("No longitude specified");
  1695.     }
  1696.     return true;
  1697.   }
  1698. };
  1699.  
  1700. Microformats.add("geo", geo_definition);
  1701.  
  1702. function tag(node, validate) {
  1703.   if (node) {
  1704.     Microformats.parser.newMicroformat(this, node, "tag", validate);
  1705.   }
  1706. }
  1707. tag.prototype.toString = function() {
  1708.   return this.tag;
  1709. }
  1710.  
  1711. var tag_definition = {
  1712.   mfObject: tag,
  1713.   attributeName: "rel",
  1714.   attributeValues: "tag",
  1715.   properties: {
  1716.     "tag" : {
  1717.       virtual: true,
  1718.       virtualGetter: function(mfnode) {
  1719.         if (mfnode.href) {
  1720.           var ioService = Components.classes["@mozilla.org/network/io-service;1"].
  1721.                                      getService(Components.interfaces.nsIIOService);
  1722.           var uri = ioService.newURI(mfnode.href, null, null);
  1723.           var url_array = uri.path.split("/");
  1724.           for(let i=url_array.length-1; i > 0; i--) {
  1725.             if (url_array[i] !== "") {
  1726.               var tag
  1727.               if (tag = Microformats.tag.validTagName(url_array[i].replace(/\+/g, ' '))) {
  1728.                 try {
  1729.                   return decodeURIComponent(tag);
  1730.                 } catch (ex) {
  1731.                   return unescape(tag);
  1732.                 }
  1733.               }
  1734.             }
  1735.           }
  1736.         }
  1737.         return null;
  1738.       }
  1739.     },
  1740.     "link" : {
  1741.       virtual: true,
  1742.       datatype: "anyURI"
  1743.     },
  1744.     "text" : {
  1745.       virtual: true
  1746.     }
  1747.   },
  1748.   validTagName: function(tag)
  1749.   {
  1750.     var returnTag = tag;
  1751.     if (tag.indexOf('?') != -1) {
  1752.       if (tag.indexOf('?') === 0) {
  1753.         return false;
  1754.       } else {
  1755.         returnTag = tag.substr(0, tag.indexOf('?'));
  1756.       }
  1757.     }
  1758.     if (tag.indexOf('#') != -1) {
  1759.       if (tag.indexOf('#') === 0) {
  1760.         return false;
  1761.       } else {
  1762.         returnTag = tag.substr(0, tag.indexOf('#'));
  1763.       }
  1764.     }
  1765.     if (tag.indexOf('.html') != -1) {
  1766.       if (tag.indexOf('.html') == tag.length - 5) {
  1767.         return false;
  1768.       }
  1769.     }
  1770.     return returnTag;
  1771.   },
  1772.   validate: function(node) {
  1773.     var tag = Microformats.parser.getMicroformatProperty(node, "tag", "tag");
  1774.     if (!tag) {
  1775.       if (node.href) {
  1776.         var url_array = node.getAttribute("href").split("/");
  1777.         for(let i=url_array.length-1; i > 0; i--) {
  1778.           if (url_array[i] !== "") {
  1779.             throw("Invalid tag name (" + url_array[i] + ")");;
  1780.           }
  1781.         }
  1782.       } else {
  1783.         throw("No href specified on tag");
  1784.       }
  1785.     }
  1786.     return true;
  1787.   }
  1788. };
  1789.  
  1790. Microformats.add("tag", tag_definition);
  1791.